/*
 * Copyright 2013-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.alibaba.cloud.nacos;

import java.util.Objects;
import java.util.Properties;

import com.alibaba.cloud.nacos.registry.NacosRegistration;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingMaintainService;
import com.alibaba.nacos.api.naming.NamingService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.cloud.client.discovery.event.InstancePreRegisteredEvent;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.context.event.EventListener;

import static com.alibaba.nacos.api.NacosFactory.createMaintainService;
import static com.alibaba.nacos.api.NacosFactory.createNamingService;
import static org.springframework.beans.BeanUtils.copyProperties;

/**
 * 容器中会注入一个 NacosServiceManager 的Bean。
 * 注意!!!该类没有构造函数，因此该类在被做成Bean后，所有属性均为null
 */
public class NacosServiceManager {

    private static final Logger log = LoggerFactory.getLogger(NacosServiceManager.class);
    /**
     * Nacos属性配置。将在yaml中配置的属性获取来
     */
    private NacosDiscoveryProperties nacosDiscoveryPropertiesCache;
    /**
     * 将由 NacosNamingService 进行实现。
     * 提供一系列API接口的实现方法，用于和nacos交互
     */
    private NamingService namingService;
    /**
     * 将由 NacosNamingMaintainService 进行实现
     */
    private NamingMaintainService namingMaintainService;

    /**
     * 返回该Bean中的 namingService 属性。
     * 显然这是一个单例设计模式的写法，因此保证在任意地方调用 getNamingService() 方法，返回全局唯一的 NamingService。
     * !!!注意，在这里并没有对线程安全进行保证。原因是在调用下方的 buildNamingService() 方法中对线程安全进行了保证。
     *
     * @param properties NacosDiscoveryProperties.getProperties()
     * @return 全局唯一的 NamingService
     */
    public NamingService getNamingService(Properties properties) {
        // 如果第一次获取namingService必然为空，因为没有构造函数。
        // 则构造NamingService
        if (Objects.isNull(this.namingService)) {
            buildNamingService(properties);
        }
        // 否则直接返回
        return namingService;
    }

    /**
     * 同上
     */
    public NamingMaintainService getNamingMaintainService(Properties properties) {
        if (Objects.isNull(namingMaintainService)) {
            buildNamingMaintainService(properties);
        }
        return namingMaintainService;
    }

    /**
     * 仅仅在构建 NacosDiscoveryProperties 中使用。
     * 用于检查yaml中的Nacos配置是否在初始化期间又出现了更改
     */
    public boolean isNacosDiscoveryInfoChanged(
            NacosDiscoveryProperties nacosDiscoveryProperties) {
        if (Objects.isNull(nacosDiscoveryPropertiesCache)
                || this.nacosDiscoveryPropertiesCache.equals(nacosDiscoveryProperties)) {
            return false;
        }
        copyProperties(nacosDiscoveryProperties, nacosDiscoveryPropertiesCache);
        return true;
    }

    /**
     * 在上方 getNamingService() 中被调用。制作一个 NamingService 的Bean并返回。
     * 同时使用 synchronized 来保证单例设计模式中的线程安全，在同步块中调用下方的 createNewNamingService()
     */
    private NamingService buildNamingService(Properties properties) {
        if (Objects.isNull(namingService)) {
            // 再次检查，然后使用synchronized串行执行，保证创建的NamingService一定是全局唯一
            synchronized (NacosServiceManager.class) {
                // 再检查
                if (Objects.isNull(namingService)) {
                    namingService = createNewNamingService(properties);
                }
            }
        }
        return namingService;
    }

    /**
     * 同上
     */
    private NamingMaintainService buildNamingMaintainService(Properties properties) {
        if (Objects.isNull(namingMaintainService)) {
            synchronized (NacosServiceManager.class) {
                if (Objects.isNull(namingMaintainService)) {
                    namingMaintainService = createNamingMaintainService(properties);
                }
            }
        }
        return namingMaintainService;
    }

    /**
     * 真正创建一个 NamingService。
     * 实际的实现类为 NacosNamingService
     */
    private NamingService createNewNamingService(Properties properties) {
        try {
            return createNamingService(properties);
        } catch (NacosException e) {
            throw new RuntimeException(e);
        }
    }

    private NamingMaintainService createNamingMaintainService(Properties properties) {
        try {
            return createMaintainService(properties);
        } catch (NacosException e) {
            throw new RuntimeException(e);
        }
    }

    public void nacosServiceShutDown() throws NacosException {
        this.namingService.shutDown();
        namingService = null;
        namingMaintainService = null;
    }

    /**
     * 一个监听器。监听器必须位于Component中，不过由于NacosServiceManager会被AutoConfiguration做成Bean，所有是没有问题的。
     *
     * @param instancePreRegisteredEvent 监听的事件 InstancePreRegisteredEvent
     */
    @EventListener
    public void onInstancePreRegisteredEvent(InstancePreRegisteredEvent instancePreRegisteredEvent) {
        Registration registration = instancePreRegisteredEvent.getRegistration();
        if (Objects.isNull(nacosDiscoveryPropertiesCache)
                && registration instanceof NacosRegistration) {
            NacosDiscoveryProperties nacosDiscoveryProperties = ((NacosRegistration) registration)
                    .getNacosDiscoveryProperties();

            nacosDiscoveryPropertiesCache = new NacosDiscoveryProperties();
            copyProperties(nacosDiscoveryProperties, nacosDiscoveryPropertiesCache);
        }
    }

}
